package com.craftinginterpreters.lox;

import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.util.List;

import static com.craftinginterpreters.lox.TokenType.*;

public class ScannerTest {

    @BeforeMethod
    public void setUp() {
        // Reset error flag before each test
        Lox.hadError = false;
    }

    @Test
    public void testScanEmptySource() {
        Scanner scanner = new Scanner("");
        List<Token> tokens = scanner.scanTokens();

        Assert.assertEquals(tokens.size(), 1, "Empty source should produce only EOF token");
        Assert.assertEquals(tokens.get(0).type, EOF, "Last token should be EOF");
        Assert.assertFalse(Lox.hadError, "There should be no errors");
    }

    @Test
    public void testScanSingleCharacterTokens() {
        Scanner scanner = new Scanner("(){},.;-+*/");
        List<Token> tokens = scanner.scanTokens();

        Assert.assertEquals(tokens.size(), 12, "Should have 10 single-character tokens plus EOF");
        Assert.assertEquals(tokens.get(0).type, LEFT_PAREN, "First token should be LEFT_PAREN");
        Assert.assertEquals(tokens.get(1).type, RIGHT_PAREN, "Second token should be RIGHT_PAREN");
        Assert.assertEquals(tokens.get(2).type, LEFT_BRACE, "Third token should be LEFT_BRACE");
        Assert.assertEquals(tokens.get(3).type, RIGHT_BRACE, "Fourth token should be RIGHT_BRACE");
        Assert.assertEquals(tokens.get(4).type, COMMA, "Fifth token should be COMMA");
        Assert.assertEquals(tokens.get(5).type, DOT, "Sixth token should be DOT");
        Assert.assertEquals(tokens.get(6).type, SEMICOLON, "Seventh token should be SEMICOLON");
        Assert.assertEquals(tokens.get(7).type, MINUS, "Eighth token should be MINUS");
        Assert.assertEquals(tokens.get(8).type, PLUS, "Ninth token should be PLUS");
        Assert.assertEquals(tokens.get(9).type, STAR, "Tenth token should be STAR");
        Assert.assertEquals(tokens.get(10).type, SLASH, "Eleventh token should be SLASH");
        Assert.assertEquals(tokens.get(11).type, EOF, "Last token should be EOF");
        Assert.assertFalse(Lox.hadError, "There should be no errors");
    }

    @Test
    public void testScanTwoCharacterTokens() {
        Scanner scanner = new Scanner("! != = == < <= > >=");
        List<Token> tokens = scanner.scanTokens();

        Assert.assertEquals(tokens.size(), 9, "Should have 9 comparison tokens plus EOF");
        Assert.assertEquals(tokens.get(0).type, BANG, "First token should be BANG");
        Assert.assertEquals(tokens.get(1).type, BANG_EQUAL, "Second token should be BANG_EQUAL");
        Assert.assertEquals(tokens.get(2).type, EQUAL, "Third token should be EQUAL");
        Assert.assertEquals(tokens.get(3).type, EQUAL_EQUAL, "Fourth token should be EQUAL_EQUAL");
        Assert.assertEquals(tokens.get(4).type, LESS, "Fifth token should be LESS");
        Assert.assertEquals(tokens.get(5).type, LESS_EQUAL, "Sixth token should be LESS_EQUAL");
        Assert.assertEquals(tokens.get(6).type, GREATER, "Seventh token should be GREATER");
        Assert.assertEquals(tokens.get(7).type, GREATER_EQUAL, "Eighth token should be GREATER_EQUAL");
        Assert.assertEquals(tokens.get(8).type, EOF, "Last token should be EOF");
        Assert.assertFalse(Lox.hadError, "There should be no errors");
    }

    @Test
    public void testScanComments() {
        Scanner scanner = new Scanner("// This is a comment\nvar x = 5; // Another comment");
        List<Token> tokens = scanner.scanTokens();

        Assert.assertEquals(tokens.size(), 6, "Comments should be ignored");
        Assert.assertEquals(tokens.get(0).type, VAR, "First token should be VAR");
        Assert.assertEquals(tokens.get(1).type, IDENTIFIER, "Second token should be IDENTIFIER");
        Assert.assertEquals(tokens.get(2).type, EQUAL, "Third token should be EQUAL");
        Assert.assertEquals(tokens.get(3).type, NUMBER, "Fourth token should be NUMBER");
        Assert.assertEquals(tokens.get(4).type, SEMICOLON, "Fifth token should be SEMICOLON");
        Assert.assertEquals(tokens.get(5).type, EOF, "Last token should be EOF");
        Assert.assertFalse(Lox.hadError, "There should be no errors");
    }

    @Test
    public void testScanStringLiterals() {
        Scanner scanner = new Scanner("\"hello\" \"world\\n\" \"\"");
        List<Token> tokens = scanner.scanTokens();

        Assert.assertEquals(tokens.size(), 4, "Should have 3 string tokens plus EOF");
        Assert.assertEquals(tokens.get(0).type, STRING, "First token should be STRING");
        Assert.assertEquals(tokens.get(0).literal, "hello", "First string literal should be 'hello'");
        Assert.assertEquals(tokens.get(1).type, STRING, "Second token should be STRING");
        Assert.assertEquals(tokens.get(1).literal, "world\\n", "Second string literal should be 'world\n'");
        Assert.assertEquals(tokens.get(2).type, STRING, "Third token should be STRING");
        Assert.assertEquals(tokens.get(2).literal, "", "Third string literal should be empty");
        Assert.assertFalse(Lox.hadError, "There should be no errors");
    }

    @Test
    public void testScanUnterminatedString() {
        Scanner scanner = new Scanner("\"unterminated string");
        scanner.scanTokens();

        Assert.assertTrue(Lox.hadError, "Should report error for unterminated string");
    }

    @Test
    public void testScanNumberLiterals() {
        Scanner scanner = new Scanner("123 45.67 0.123 42.");
        List<Token> tokens = scanner.scanTokens();

        Assert.assertEquals(tokens.size(), 6, "Should have 6 number tokens plus EOF");
        Assert.assertEquals(tokens.get(0).type, NUMBER, "First token should be NUMBER");
        Assert.assertEquals(tokens.get(0).literal, 123.0, "First number should be 123.0");
        Assert.assertEquals(tokens.get(1).type, NUMBER, "Second token should be NUMBER");
        Assert.assertEquals(tokens.get(1).literal, 45.67, "Second number should be 45.67");
        Assert.assertEquals(tokens.get(2).type, NUMBER, "Third token should be NUMBER");
        Assert.assertEquals(tokens.get(2).literal, 0.123, "Third number should be 0.123");
        Assert.assertEquals(tokens.get(3).type, NUMBER, "Fourth token should be NUMBER");
        Assert.assertEquals(tokens.get(3).literal, 42.0, "Fourth number should be 42.0");
        Assert.assertFalse(Lox.hadError, "There should be no errors");
    }

    @Test
    public void testScanIdentifiers() {
        Scanner scanner = new Scanner("hello world _test variable1");
        List<Token> tokens = scanner.scanTokens();

        Assert.assertEquals(tokens.size(), 5, "Should have 4 identifier tokens plus EOF");
        Assert.assertEquals(tokens.get(0).type, IDENTIFIER, "First token should be IDENTIFIER");
        Assert.assertEquals(tokens.get(0).lexeme, "hello", "First identifier should be 'hello'");
        Assert.assertEquals(tokens.get(1).type, IDENTIFIER, "Second token should be IDENTIFIER");
        Assert.assertEquals(tokens.get(1).lexeme, "world", "Second identifier should be 'world'");
        Assert.assertEquals(tokens.get(2).type, IDENTIFIER, "Third token should be IDENTIFIER");
        Assert.assertEquals(tokens.get(2).lexeme, "_test", "Third identifier should be '_test'");
        Assert.assertEquals(tokens.get(3).type, IDENTIFIER, "Fourth token should be IDENTIFIER");
        Assert.assertEquals(tokens.get(3).lexeme, "variable1", "Fourth identifier should be 'variable1'");
        Assert.assertFalse(Lox.hadError, "There should be no errors");
    }

    @Test
    public void testScanKeywords() {
        Scanner scanner = new Scanner("and class else false for fun if nil or print return super this true var while");
        List<Token> tokens = scanner.scanTokens();

        Assert.assertEquals(tokens.size(), 17, "Should have 16 keyword tokens plus EOF");
        Assert.assertEquals(tokens.get(0).type, AND, "First token should be AND");
        Assert.assertEquals(tokens.get(1).type, CLASS, "Second token should be CLASS");
        Assert.assertEquals(tokens.get(2).type, ELSE, "Third token should be ELSE");
        Assert.assertEquals(tokens.get(3).type, FALSE, "Fourth token should be FALSE");
        Assert.assertEquals(tokens.get(4).type, FOR, "Fifth token should be FOR");
        Assert.assertEquals(tokens.get(5).type, FUN, "Sixth token should be FUN");
        Assert.assertEquals(tokens.get(6).type, IF, "Seventh token should be IF");
        Assert.assertEquals(tokens.get(7).type, NIL, "Eighth token should be NIL");
        Assert.assertEquals(tokens.get(8).type, OR, "Ninth token should be OR");
        Assert.assertEquals(tokens.get(9).type, PRINT, "Tenth token should be PRINT");
        Assert.assertEquals(tokens.get(10).type, RETURN, "Eleventh token should be RETURN");
        Assert.assertEquals(tokens.get(11).type, SUPER, "Twelfth token should be SUPER");
        Assert.assertEquals(tokens.get(12).type, THIS, "Thirteenth token should be THIS");
        Assert.assertEquals(tokens.get(13).type, TRUE, "Fourteenth token should be TRUE");
        Assert.assertEquals(tokens.get(14).type, VAR, "Fifteenth token should be VAR");
        Assert.assertEquals(tokens.get(15).type, WHILE, "Sixteenth token should be WHILE");
        Assert.assertFalse(Lox.hadError, "There should be no errors");
    }

    @Test
    public void testScanComplexExpression() {
        Scanner scanner = new Scanner("var x = 10 + 5 * (y - 3);");
        List<Token> tokens = scanner.scanTokens();

        Assert.assertEquals(tokens.size(), 14, "Should have 13 tokens plus EOF");
        Assert.assertEquals(tokens.get(0).type, VAR, "First token should be VAR");
        Assert.assertEquals(tokens.get(1).type, IDENTIFIER, "Second token should be IDENTIFIER");
        Assert.assertEquals(tokens.get(2).type, EQUAL, "Third token should be EQUAL");
        Assert.assertEquals(tokens.get(3).type, NUMBER, "Fourth token should be NUMBER");
        Assert.assertEquals(tokens.get(4).type, PLUS, "Fifth token should be PLUS");
        Assert.assertEquals(tokens.get(5).type, NUMBER, "Sixth token should be NUMBER");
        Assert.assertEquals(tokens.get(6).type, STAR, "Seventh token should be STAR");
        Assert.assertEquals(tokens.get(7).type, LEFT_PAREN, "Eighth token should be LEFT_PAREN");
        Assert.assertEquals(tokens.get(8).type, IDENTIFIER, "Ninth token should be IDENTIFIER");
        Assert.assertEquals(tokens.get(9).type, MINUS, "Tenth token should be MINUS");
        Assert.assertEquals(tokens.get(10).type, NUMBER, "Eleventh token should be NUMBER");
        Assert.assertEquals(tokens.get(11).type, RIGHT_PAREN, "Twelfth token should be RIGHT_PAREN");
        Assert.assertEquals(tokens.get(12).type, SEMICOLON, "Thirteenth token should be SEMICOLON");
        Assert.assertFalse(Lox.hadError, "There should be no errors");
    }

    @Test
    public void testScanWithWhitespace() {
        Scanner scanner = new Scanner("  \t\nvar\nx=\t5;\n  ");
        List<Token> tokens = scanner.scanTokens();

        Assert.assertEquals(tokens.size(), 6, "Whitespace should be ignored");
        Assert.assertEquals(tokens.get(0).type, VAR, "First token should be VAR");
        Assert.assertEquals(tokens.get(1).type, IDENTIFIER, "Second token should be IDENTIFIER");
        Assert.assertEquals(tokens.get(2).type, EQUAL, "Third token should be EQUAL");
        Assert.assertEquals(tokens.get(3).type, NUMBER, "Fourth token should be NUMBER");
        Assert.assertEquals(tokens.get(4).type, SEMICOLON, "Fifth token should be SEMICOLON");
        Assert.assertFalse(Lox.hadError, "There should be no errors");
    }

    @Test
    public void testScanWithError() {
        Scanner scanner = new Scanner("var x = @ 5;");
        List<Token> tokens = scanner.scanTokens();

        Assert.assertTrue(Lox.hadError, "Should report error for invalid character");
        // Even with errors, it should continue scanning
        Assert.assertTrue(tokens.size() > 1, "Should continue scanning after error");
    }
}